---------------------------------------------------------------
-- @Copyright：           wuwenkui
-- @Description：         请求token授权认证

-- @modify time           2021-06-18
---------------------------------------------------------------
require "modules.business_advert.advert_define"

local b_common     = require("modules.business_common.business_common");
local biz_auth = require("modules.business_common.business_auth")

local FOO = {
	_VERSION = '1.1.0',
	normal_exit = nil,
	error_exit  = nil,
	ns_http_sec = ns_auth_comm_checker.ns_http_sec,
	genS2Salt   = ns_auth_comm_checker.genS2Salt,
    --
    one_share_time_out = 60 * 60, --默认缓存60分钟
    one_diff_time_out = 60 * 5 --默认误差5分钟
}

FOO.init = function()
    if ns_config.debug == 1 then
        FOO.one_diff_time_out = 60 * 60 --测试环境默认误差60分钟
    end
end

-- 检查uin合法性 不退出
FOO.check_uin_safe = function( uin_ )
	local uin = tonumber( uin_ ) or 0
	if  uin < 1000 then
		log.error( "uin error=" .. (uin_ or 'nil') )
		return false
	end
	ngx.ctx.m_params.uin = uin
	return true
end

-- 设置出错回调 ( 设置自己定义的出错处理 可选 )
FOO.set_error_exit = function( normal_exit_, error_exit_ )
	--log.debug( "call set_error_exit" );
	if  FOO.error_exit then
		--已经设置
	else
		FOO.normal_exit = normal_exit_;    ---普通业务错误
		FOO.error_exit  = error_exit_;     ---系统严重错误
	end
end

FOO.error_exit = function( txt )
	log.error( txt )
	ngx.exit( ngx.ERROR );
end

FOO.checkMd5 = function( auth_, time_, uin_ )
	local md5 = ""
	if  ngx.ctx.m_params.act then
		md5 = ngx.md5 ( time_ .. FOO.genS2Salt() .. uin_ )
	else
		md5 = ngx.md5 ( time_ .. '#miniw_907#'  .. uin_ )
	end

    log.debug('you auth:' .. auth_ .. ' system auth:' .. md5)

	if  md5 == string.lower( auth_ ) then
		return true
	else
		return false
	end
end

FOO.get_abtest_token = function(uin_, time_, cmd_)
    return ns_auth_comm_checker.gen_comm_inner_token(uin_, time_, cmd_)
end

FOO.get_cmd_token = function(uin_, time_, cmd_)
    return ns_auth_comm_checker.gen_comm_inner_token(uin_, time_, cmd_)
end

--[[
实现逻辑 --lee 2021 08 06
一次请求默认缓存60分钟的key, 在这个60分钟内,一次有效
缓存失效后,根据时间来判定
玩家倒退时间,导致与当前时间超过60分钟失效
玩家前进时间,导致与当前时间超过60分钟也失败
防止缓存满了后,还未到60分钟的key遭到释放,故判定的时间一定要小于缓存时间

比如60分钟的key 在0<N<60分钟的时候遭到释放,这个时候就要靠时间去判定了
因为如果客户端取到的是服务器时间,允许的误差在1分钟内
如果在这1分钟内,请求成功了,那又会有60分钟的key挡住

也许会认为有一种情况会出现刷2次的情况,就是在1分钟内请求后,key立马被释放了,然后再次发送请求,

但实际上会被nginx下的share_dict的释放逻辑挡住,不会去释放最近的数据,所以不会出现刷2次的情况
--]]
--设置有效请求
local function set_one_request(dict_name)
    local flag = table.tostring(ngx.ctx.m_params)
    local user_md5 = ngx.md5(flag)
    b_common.business_nginx_share.set_nginx_share(dict_name, user_md5, {1}, FOO.one_share_time_out) --默认缓存60分钟
end

--share_dict校验
local function check_one_request_by_judge(dict_name)
    local flag = table.tostring(ngx.ctx.m_params)
    local user_md5 = ngx.md5(flag)
    local flag_share = b_common.business_nginx_share.get_nginx_share(dict_name, user_md5) or {}
    if uu.tableIsEmpty(flag_share) then
        return true --在一次有效内
    end
    return false
end

--时间校验
local function check_one_request_by_time (time_)
    local time = time_
    if time == nil then
        time = tonumber(ngx.ctx.m_params.time) or 0
    end
    local diff_time = math_abs(uu.now() - time)
    if diff_time < FOO.one_diff_time_out then
        return true --绝对值在one_diff_time_out秒内算是正常
    end
    return false, diff_time
end

--不是第一次请求
FOO.not_first_request = function(reason, time_)
    -- 内网环境和海外都 跳过检测 
    if (ns_config.debug==1) or (ns_config.debug==11) then
        if ngx.ctx.m_params.test then
            local test = tonumber(ngx.ctx.m_params.test) or 0
            if test == 1 then --test == 1的时候 跳过检测
                return true
            end
        end
    end
    local time = time_
    if time == nil then
        time = tonumber(ngx.ctx.m_params.time) or 0
    end

    --需要检测的函数
    if ns_config.check_one_req and ns_config.check_one_req[ngx.ctx.m_params.act] == 1 then
        --有效请求判定
        local dict_name = ns_config.dict_name.one_request .. ((ngx.ctx.m_params.uin or 0)  % 10)
        --一次请求--缓存命中
        if not check_one_request_by_judge(dict_name) then
            log.debug("business_one_request|1|" .. reason .. "|" .. ngx.ctx.m_params.uin .. "|" .. ngx.ctx.m_params.act .. "|" .. time .. "|" .. uu.now() .. "|" .. uu.now() - time)
            log.day_list("business_one_request", "business_one_request", 1, reason, ngx.ctx.m_params.uin, ngx.ctx.m_params.act, time, uu.now(), uu.now() - time)
            return false, {result = ERROR_CODE.OR_SUCCESS, msg = "客户端的多次相同的请求过滤"}
        else
            --缓存未命中
            --判定时间,没有超过默认阈值
            local ret, diff_time = check_one_request_by_time(time)
            if not ret then
                --时间超过了默认阈值,防止多次请求,再一次丢到缓存内
                set_one_request(dict_name)
                log.debug("business_one_request|2|" .. reason .. "|" .. (ngx.ctx.m_params.uin or "uin==nil") .. "|" .. ngx.ctx.m_params.act .. "|" .. time .. "|" .. uu.now() .. "|" .. diff_time)
                log.day_list("business_one_request", "business_one_request", 2, reason, ngx.ctx.m_params.uin, ngx.ctx.m_params.act, time, uu.now(), diff_time)
                return false, {result = ERROR_CODE.ONE_REQ_ERROR, msg = "客户端时间与服务器时间相差过大"}
            end
        end
        --设置有效请求key
        set_one_request(dict_name)
    end
    return true
end

-------------------------------------------------------------
-- MD5 加密版本
-------------------------------------------------------------
--直接用common的md5,
FOO.check_encrypt_by_ver = biz_auth.check_md5_by_version

-- 需要进行MD5加密的参数列表 （如果参数有以下参数，则该参数需要进行md5校验）
--\这个还必须和客户端一一对应，而客户端对应的是common里的字段，所以直接用comm加密了
local check_md5_param = {
    --  必带参数
    act = 1,
    time = 1,
    apiid = 1,
    ver = 1,
    uin = 1,
    s2t = 1,
    -- 通用参数 通用的名字方便其他功能直接使用
    param_id = 1,
    param_num = 1,
    -- 自定义参数
    type = 1,
    num = 1,
    buy_times = 1,
    times = 1,
    gift_id = 1,
    item_id = 1,
    buy_id = 1,
    encrypt_ver = 1,
    itemnum = 1,
    production_id = 1,
    taskidx = 1,
    skinid = 1,
    headIndex = 1,
    status = 1,
    content = 1,
    platform = 1,
    itemid = 1,
    fee_type = 1,
    value = 1,
    id = 1,
    auth = 1,
    friend_uin = 1,
    fee_id = 1,
    target = 1,
    title = 1,
    iswarehouse = 1,
    avatarUrl = 1,
    coin_num = 1,
    op_ret = 1,
    event = 1,
    prices_id = 1,
    nickname = 1,
    awardid = 1,
    price = 1,
    ishide = 1,
    cost = 1,
    ad_id = 1,
    survival = 1,
    pricetype = 1,
    itemtype = 1,
    position_id = 1,
    platform_id = 1,
}
--*********************************************

FOO.check_encrypt_by_ver_bak = function()
    local encrypt_ver = tonumber(ngx.ctx.m_params.encrypt_ver) or 0
    local ENCRYPT_ENUM = {
        md5_old = 0,      --旧的md5加密方式，默认走这个
        md5_new = 1,      --在需要加密参数列表里的参数，都需要参与加密
    }
    return ((({
        [ENCRYPT_ENUM.md5_old] = FOO.check_token,
        [ENCRYPT_ENUM.md5_new] = FOO.check_auth,
    })[encrypt_ver]) or function() return false end)()
end

-- 检查命令数据的合法性
FOO.check_auth = function()
    log.debug("=========is md5 in new ver")
    local private_key = ns_config.business_key_new.act
    local param_list = {}
    for k, _ in pairs(ngx.ctx.m_params or {}) do
        if check_md5_param[k] then
            table.insert(param_list, k)
        end
    end

	-- 验证签名
	local auth_md5 = FOO.make_md5(ngx.ctx.m_params, param_list, private_key)
	if not auth_md5 then
		-- 参数不存在
		log.day_list( ADVERT, "not auth_md5 error" )
		return false
	end

	if auth_md5 ~= (ngx.ctx.m_params.md5 or "") then
		-- 验签失败
		log.day_list( ADVERT, "param_check  param_list="..uu.to_str(param_list).." auth_md5="..uu.to_str(auth_md5)
        .." ngx.ctx.m_params.auth="..uu.to_str(ngx.ctx.m_params.auth)
        .." gx.ctx.m_params.uin="..uu.to_str(ngx.ctx.m_params.uin))
		return false
	end

	return true
end

FOO.make_md5 = function(request_list, param_list, private_key)
	table.sort(param_list, function(left, right) if left < right then return true end end)

	local base_str = FOO.pack_base_str(request_list, param_list)
	if not base_str then
		return false
	end

	local auth_md5 = ngx.md5(base_str .. private_key)
	return auth_md5
end


FOO.pack_base_str = function(request_list, param_list, code_fun)
	local base_str = false
	for i=1, #param_list do
		local key   = param_list[i]
		local value = request_list[key]
		if value then
			local code_value = value
			if code_fun then
				code_value = ngx.escape_uri(value)
			end

			if not base_str then
				base_str = key .. "=" .. code_value
			else
				base_str = base_str .. "&" .. key .. "=" .. code_value
			end
		else
			log.error(string.format("key not exist:[key=%s]", key))
			return false
		end
	end

	return base_str
end



return FOO
